/*   The MIT License
 *   
 *   Carina Common
 *   Copyright (c) 2010-2011 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
#endif

#include "carina/common/ces-file.hh"

#include <cstring>

namespace Carina
{
size_t copy_string_to_fixed(char* dst, size_t dst_size, const char* src)
{
    size_t i, n = 0;
    assert(dst_size);
    for(i = 0; src[i]; ++i)
    {
        if(i == dst_size - 1)
        {
            n = strlen(src + i);
            break;
        }
        dst[i] = src[i];
    }
    dst[i] = 0;
    return n;
}

CESFile::CESFile()
{
    m_Header.magic_number = ces_magic_number;
    m_Header.major_version = ces_major_version;
    m_Header.minor_version = ces_minor_version;
    m_Root.attributes().setAttribute("Node type", "Root");
}

CESFile::~CESFile()
{
}

CESFile::CESFile(const CESFile& ces_file)
    :   m_Header(ces_file.m_Header),
        m_Geometry(ces_file.m_Geometry),
        m_Entities(ces_file.m_Entities),
        m_Lighting(ces_file.m_Lighting),
        m_Root(ces_file.m_Root)
{
}

CESFile& CESFile::operator=(const CESFile& ces_file)
{
    m_Header = ces_file.m_Header;
    m_Geometry = ces_file.m_Geometry;
    m_Entities = ces_file.m_Entities;
    m_Lighting = ces_file.m_Lighting;
    m_Root = ces_file.m_Root;
    return *this;
}

void CESFile::clear()
{
    m_Geometry.clear();
    m_Entities.clear();
    m_Lighting.clear();
    m_Root.clear();
}

bool CESFile::save(const string& filename) const
{
    std::ofstream fs(filename.c_str(), std::ios::binary | std::ios::out);
    if(!fs)
        return false;
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESHeader));
    m_Geometry.write(fs);
    m_Entities.write(fs);
    m_Lighting.write(fs);
    m_Root.write(fs);
    return true;
}

bool CESFile::open(const string& filename)
{
    std::ifstream fs(filename.c_str(), std::ios::binary | std::ios::in);
    if(!fs)
        return false;
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESHeader));
    if(m_Header.magic_number != ces_magic_number ||
       m_Header.major_version != ces_major_version ||
       m_Header.minor_version != ces_minor_version)
        return false;

    return m_Geometry.read(fs) && m_Entities.read(fs) && m_Lighting.read(fs) && m_Root.read(fs);
}

CESNode& CESFile::getRoot()
{
    return m_Root;
}

CESGeometrySection& CESFile::getGeometrySection()
{
    return m_Geometry;
}

CESEntitySection& CESFile::getEntitySection()
{
    return m_Entities;
}

CESGlobalLightSection& CESFile::getGlobalLightSection()
{
    return m_Lighting;
}

CESMesh::CESMesh()
    :   m_Skeleton(CE_ALLOCATE(CESSkeleton))
{
}

CESMesh::CESMesh(const CESMesh& ces_mesh)
    :   m_Skeleton(CE_ALLOCATE(CESSkeleton)(*ces_mesh.m_Skeleton)),
        m_Indices(ces_mesh.m_Indices),
        m_PointData(ces_mesh.m_PointData),
        m_Submeshes(ces_mesh.m_Submeshes)
{
    for(size_t i = 0; i < ces_mesh.m_Animations.getCount(); ++i)
        m_Animations.addAnimation(make_aligned_shared<CESAnimation>(*ces_mesh.m_Animations.getAnimation(i), m_Skeleton));
}

CESMesh::~CESMesh()
{
}

CESMesh& CESMesh::operator=(const CESMesh& ces_mesh)
{
    *m_Skeleton = *ces_mesh.m_Skeleton;
    m_Indices = ces_mesh.m_Indices;
    m_PointData = ces_mesh.m_PointData;
    m_Submeshes = ces_mesh.m_Submeshes;
    m_Animations.clear();
    for(size_t i = 0; i < ces_mesh.m_Animations.getCount(); ++i)
        m_Animations.addAnimation(make_aligned_shared<CESAnimation>(*ces_mesh.m_Animations.getAnimation(i), m_Skeleton));
    return *this;
}

bool CESMesh::read(std::istream& fs)
{
    CESMeshHeader header;
    fs.read(reinterpret_cast<char*>(&header), sizeof(CESMeshHeader));
    if(!fs)
        return false;
    m_Indices.resize(header.indices);
    fs.read(reinterpret_cast<char*>(&m_Indices.front()), sizeof(m_Indices[0])*m_Indices.size());
    if(!fs)
        return false;
    if(!header.submeshes)
        return false;
    m_Submeshes.resize(header.submeshes);
    for(size_t i = 0; i < m_Submeshes.size(); ++i)
        if(!m_Submeshes[i].read(fs))
            return false;
    if(header.is_skinned)
    {
        m_PointData.resize(header.vertex_data * ces_skinned_size_float);
        fs.read(reinterpret_cast<char*>(&m_PointData.front()), sizeof(m_PointData[0])*m_PointData.size());
        if(!fs || !m_Skeleton->read(fs) || !m_Animations.read(fs, m_Skeleton))
            return false;
    }
    else
    {
        m_PointData.resize(header.vertex_data * ces_static_size_float);
        fs.read(reinterpret_cast<char*>(&m_PointData.front()), sizeof(m_PointData[0])*m_PointData.size());
    }

    return fs.good();
}

void CESMesh::write(std::ostream& fs) const
{
    CESMeshHeader header;
    size_t point_size = hasSkeleton() ? ces_skinned_size_float : ces_static_size_float;
    header.indices = m_Indices.size();
    header.vertex_data = m_PointData.size() / point_size;
    header.submeshes = m_Submeshes.size();
    header.is_skinned = hasSkeleton();
    fs.write(reinterpret_cast<const char*>(&header), sizeof(CESMeshHeader));
    fs.write(reinterpret_cast<const char*>(&m_Indices.front()), sizeof(m_Indices[0])*m_Indices.size());
    assert(!m_Submeshes.empty());
    for(size_t i = 0; i < m_Submeshes.size(); ++i)
        m_Submeshes[i].write(fs);
    if(header.is_skinned)
    {
        assert(m_PointData.size() % ces_skinned_size_float == 0);
        fs.write(reinterpret_cast<const char*>(&m_PointData.front()), sizeof(m_PointData[0])*m_PointData.size());
        m_Skeleton->write(fs);
        m_Animations.write(fs);
    }
    else
    {
        assert(m_PointData.size() % ces_static_size_float == 0);
        fs.write(reinterpret_cast<const char*>(&m_PointData.front()), sizeof(m_PointData[0])*m_PointData.size());
    }
}

void CESMesh::setIndices(const IndicesArray& indices)
{
    m_Indices = indices;
}

const IndicesArray& CESMesh::getIndices() const
{
    return m_Indices;
}

void CESMesh::setData(const PointDataArray& data)
{
    assert(data.size() % (m_Skeleton->empty() ? ces_static_size_float : ces_skinned_size_float) == 0);
    m_PointData = data;
}

const PointDataArray& CESMesh::getPointData() const
{
    return m_PointData;
}

CESSkeletonPtr CESMesh::getSkeleton()
{
    return m_Skeleton;
}

CESAnimationPtr CESMesh::addAnimation(const string& name)
{
    CESAnimationPtr anim(CE_ALLOCATE(CESAnimation)(name, m_Skeleton));
    m_Animations.addAnimation(anim);
    return anim;
}

CESSubmesh& CESMesh::addSubmesh()
{
    m_Submeshes.push_back(CESSubmesh());
    return m_Submeshes.back();
}

CESSubmesh& CESMesh::getSubmesh(size_t idx)
{
    return m_Submeshes[idx];
}

size_t CESMesh::getSubmeshCount() const
{
    return m_Submeshes.size();
}

bool CESMesh::hasSkeleton() const
{
    return !m_Skeleton->empty();
}

CESAnimationSection& CESMesh::getAnimations()
{
    assert(hasSkeleton());
    return m_Animations;
}

CESSubmesh::CESSubmesh()
{
    m_Header.begin_index = 0;
    m_Header.indices = 0;
    m_Header.material[0] = 0;
}

CESSubmesh::~CESSubmesh()
{
}

CESSubmesh::CESSubmesh(const CESSubmesh& ces_sm)
    :   m_Header(ces_sm.m_Header),
        m_Attributes(ces_sm.m_Attributes)
{
}

CESSubmesh& CESSubmesh::operator=(const CESSubmesh& ces_sm)
{
    m_Header = ces_sm.m_Header;
    m_Attributes = ces_sm.m_Attributes;
    return *this;
}

void CESSubmesh::setCount(size_t c)
{
    m_Header.indices = c;
}

void CESSubmesh::setOffset(size_t c)
{
    m_Header.begin_index = c;
}

size_t CESSubmesh::getCount() const
{
    return m_Header.indices;
}

size_t CESSubmesh::getOffset() const
{
    return m_Header.begin_index;
}

void CESSubmesh::setMaterial(const string& name)
{
    copy_string_to_fixed(m_Header.material, sizeof(CESSubmeshHeader().material), name.c_str());
}

string CESSubmesh::getMaterial() const
{
    return m_Header.material;
}

bool CESSubmesh::read(std::istream& fs)
{
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESSubmeshHeader));
    if(!fs)
        return false;
    return m_Attributes.read(fs);
}

void CESSubmesh::write(std::ostream& fs) const
{
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESSubmeshHeader));
    m_Attributes.write(fs);
}

CESAttributeContainer& CESSubmesh::attributes()
{
    return m_Attributes;
}

CESSkeleton::CESSkeleton()
{
    m_Header.bone_count = 0;
    m_Header.node_count = 0;
    m_Header.root_bone = 0;
}

CESSkeleton::~CESSkeleton()
{
}

CESSkeleton::CESSkeleton(const CESSkeleton& ces_skel)
    :   m_Header(ces_skel.m_Header),
        m_Nodes(ces_skel.m_Nodes)
{
}

CESSkeleton& CESSkeleton::operator=(const CESSkeleton& ces_skel)
{
    m_Header = ces_skel.m_Header;
    m_Nodes = ces_skel.m_Nodes;
    return *this;
}

bool CESSkeleton::read(std::istream& fs)
{
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESSkeletonHeader));
    if(!fs)
        return false;
    m_Nodes.resize(m_Header.node_count);
    for(size_t i = 0; i < m_Nodes.size(); ++i)
        if(!m_Nodes[i].read(fs))
            return false;
    return true;
}

void CESSkeleton::write(std::ostream& fs) const
{
    assert(m_Header.node_count == m_Nodes.size() && m_Header.bone_count < m_Header.node_count);
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESSkeletonHeader));
    for(size_t i = 0; i < m_Nodes.size(); ++i)
        m_Nodes[i].write(fs);
}

bool CESSkeleton::empty() const
{
    return m_Nodes.empty();
}

CESBone& CESSkeleton::getNode(size_t i)
{
    return m_Nodes[i];
}

size_t CESSkeleton::getBoneCount() const
{
    return m_Header.bone_count;
}

size_t CESSkeleton::getNodeCount() const
{
    return m_Header.node_count;
}

void CESSkeleton::setRoot(size_t idx)
{
    m_Header.root_bone = idx;
}

size_t CESSkeleton::getRoot() const
{
    return m_Header.root_bone;
}

void CESSkeleton::setCount(size_t bones, size_t nodes_total)
{
    assert(bones <= nodes_total);
    m_Header.bone_count = bones;
    m_Header.node_count = nodes_total;
    m_Nodes.resize(nodes_total);
}

CESBone::CESBone()
{
    m_Header.children_count = 0;
}

CESBone::~CESBone()
{
}

CESBone::CESBone(const CESBone& ces_bone)
    :   m_Header(ces_bone.m_Header),
        m_Children(ces_bone.m_Children)
{
}

CESBone& CESBone::operator=(const CESBone& ces_bone)
{
    m_Header = ces_bone.m_Header;
    m_Children = ces_bone.m_Children;
    return *this;
}

bool CESBone::read(std::istream& fs)
{
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESBoneHeader));
    if(!fs)
        return false;
    m_Children.resize(m_Header.children_count);
    fs.read(reinterpret_cast<char*>(&m_Children.front()), sizeof(m_Children[0])*m_Children.size());
    return fs.good();
}

void CESBone::write(std::ostream& fs) const
{
    assert(m_Header.children_count == m_Children.size());
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESBoneHeader));
    fs.write(reinterpret_cast<const char*>(&m_Children.front()), sizeof(m_Children[0])*m_Children.size());
}

float* CESBone::getInvTransform(size_t i)
{
    return m_Header.q_inv_transform.quat[i].elem;
}

float* CESBone::getTransform(size_t i)
{
    return m_Header.q_transform.quat[i].elem;
}

void CESBone::setChildren(const DataVector<bone_child_t>& children)
{
    m_Children.assign(children.begin(), children.end());
    m_Header.children_count = children.size();
}

size_t CESBone::getChildrenCount() const
{
    return m_Children.size();
}

bone_child_t CESBone::getChild(size_t idx) const
{
    return m_Children[idx];
}

CESAnimationSection::CESAnimationSection()
{
    m_Header.count = 0;
}

void CESAnimationSection::addAnimation(const CESAnimationPtr& anim)
{
    m_Animations.push_back(anim);
    ++m_Header.count;
}

void CESAnimationSection::clear()
{
    m_Header.count = 0;
    m_Animations.clear();
}

bool CESAnimationSection::read(std::istream& fs, const CESSkeletonPtr& skel)
{
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESAnimationSectionHeader));
    if(!fs)
        return false;
    m_Animations.resize(m_Header.count);
    for(size_t i = 0; i < m_Animations.size(); ++i)
    {
		CESAnimationPtr anim(make_aligned_shared<CESAnimation>(skel));
        if(!anim->read(fs))
            return false;
        m_Animations[i] = anim;
    }
    return true;
}

void CESAnimationSection::write(std::ostream& fs) const
{
    assert(m_Header.count == m_Animations.size());
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESAnimationSectionHeader));
    for(size_t i = 0; i < m_Animations.size(); ++i)
        m_Animations[i]->write(fs);
}

CESAnimationPtr CESAnimationSection::getAnimation(size_t idx)
{
    return m_Animations[idx];
}

CESAnimationConstPtr CESAnimationSection::getAnimation(size_t idx) const
{
    return m_Animations[idx];
}

size_t CESAnimationSection::getCount() const
{
    return m_Animations.size();
}

CESBoneAnim::CESBoneAnim()
{
}

CESBoneAnim::~CESBoneAnim()
{
}

CESBoneAnim::CESBoneAnim(const CESBoneAnim& ces_banim)
    :   m_Header(ces_banim.m_Header)
{
}

CESBoneAnim& CESBoneAnim::operator=(const CESBoneAnim& ces_banim)
{
    m_Header = ces_banim.m_Header;
    return *this;
}

float* CESBoneAnim::getTransform(size_t i)
{
    return m_Header.q_transform.quat[i].elem;
}

CESAnimation::CESAnimation(const CESSkeletonPtr& skel)
    :   m_Skeleton(skel)
{
    m_Header.keyframes = 0;
}

CESAnimation::CESAnimation(const string& name, const CESSkeletonPtr& skel)
    :   m_Skeleton(skel)
{
    m_Header.keyframes = 0;
    copy_string_to_fixed(m_Header.name, sizeof(CESAnimationHeader().name), name.c_str());
}

CESAnimation::CESAnimation(const CESAnimation& ces_anim, const CESSkeletonPtr& skel)
    :   m_Header(ces_anim.m_Header),
        m_Skeleton(skel)
{
}

void CESAnimation::setKeyframes(const std::vector<float>& k)
{
    m_Keyframes = k;
    m_Header.keyframes = k.size();
    m_BoneAnims.clear();
    m_BoneAnims.resize(k.size()*getNodeCount());
}

CESBoneAnim& CESAnimation::getNode(size_t k, size_t i)
{
    size_t nc = getNodeCount();
    assert(k < m_Keyframes.size());
    assert(i < nc);
    return m_BoneAnims[k*nc + i];
}

size_t CESAnimation::getBoneCount() const
{
    return m_Skeleton->getBoneCount();
}

size_t CESAnimation::getNodeCount() const
{
    return m_Skeleton->getNodeCount();
}

size_t CESAnimation::getKeyframesCount() const
{
    return m_Keyframes.size();
}

float CESAnimation::getKeyframeTime(size_t idx) const
{
    return m_Keyframes[idx];
}

bool CESAnimation::read(std::istream& fs)
{
    size_t nc = m_Skeleton->getNodeCount();
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESAnimationHeader));
    if(!fs)
        return false;
    m_Keyframes.resize(m_Header.keyframes);
    m_BoneAnims.resize(nc*m_Keyframes.size());
    for(size_t i = 0; i < m_Keyframes.size(); ++i)
    {
        fs.read(reinterpret_cast<char*>(&m_Keyframes[i]), sizeof(m_Keyframes[0]));
        if(!fs)
            return false;
        fs.read(reinterpret_cast<char*>(&m_BoneAnims[nc*i]),
                sizeof(m_BoneAnims[0])*nc);
        if(!fs)
            return false;
    }
    return true;
}

void CESAnimation::write(std::ostream& fs) const
{
    size_t nc = m_Skeleton->getNodeCount();
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESAnimationHeader));
    for(size_t i = 0; i < m_Keyframes.size(); ++i)
    {
        fs.write(reinterpret_cast<const char*>(&m_Keyframes[i]), sizeof(m_Keyframes[0]));
        fs.write(reinterpret_cast<const char*>(&m_BoneAnims[nc*i]), 
                sizeof(m_BoneAnims[0])*nc);
    }
}

string CESAnimation::getName() const
{
    return m_Header.name;
}

CESAttribute::CESAttribute()
{
    m_Header.type = CES_UNKNOWN;
}

CESAttribute::CESAttribute(const string& name)
{
    copy_string_to_fixed(m_Header.name, sizeof(CESAttributeHeader().name), name.c_str());
    m_Header.type = CES_UNKNOWN;
}

CESAttribute::~CESAttribute()
{
}

CESAttribute::CESAttribute(const CESAttribute& ces_attr)
    :   m_Header(ces_attr.m_Header),
        m_Value(ces_attr.m_Value ? ces_attr.m_Value->clone() : CESAttributeValuePtr())
{
}

CESAttribute& CESAttribute::operator=(const CESAttribute& ces_attr)
{
    m_Header = ces_attr.m_Header;
    m_Value = ces_attr.m_Value ? ces_attr.m_Value->clone() : CESAttributeValuePtr();
    return *this;
}

bool CESAttribute::operator==(const string& attr_name) const
{
    return m_Header.name == attr_name;
}

string CESAttribute::getName() const
{
    return m_Header.name;
}

CESVarType CESAttribute::getType() const
{
    return (CESVarType)m_Header.type;
}

CESVarType CESAttribute::getArrayType() const
{
    if(m_Header.type != CES_ARRAY)
        return CES_UNKNOWN;
    return static_cast<CESAttributeArray&>(*m_Value).getType();
}

void CESAttribute::set(const char* str)
{
    fixed_string_t fstr;
    copy_string_to_fixed(fstr.str, sizeof(fixed_string_t().str), str);
    set(fstr);
}

void CESAttribute::set(const string& str)
{
    set(str.c_str());
}

bool CESAttribute::get(string& str) const
{
    fixed_string_t fstr;
    if(!get(fstr))
        return false;
    str = fstr.str;
    return true;
}

bool CESAttribute::read(std::istream& fs)
{
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESAttributeHeader));
    if(!fs)
        return false;
    switch(m_Header.type)
    {
    case CES_ARRAY:   m_Value = make_aligned_shared<CESAttributeArray>(); break;
    case CES_FLOAT:   m_Value = make_aligned_shared<CESAttributeVar<float>>(); break;
    case CES_INT:     m_Value = make_aligned_shared<CESAttributeVar<int32>>(); break;
    case CES_SHORT:   m_Value = make_aligned_shared<CESAttributeVar<int16>>(); break;
    case CES_BYTE:    m_Value = make_aligned_shared<CESAttributeVar<int8>>(); break;
    case CES_UINT:    m_Value = make_aligned_shared<CESAttributeVar<uint32>>(); break;
    case CES_USHORT:  m_Value = make_aligned_shared<CESAttributeVar<uint16>>(); break;
    case CES_UBYTE:   m_Value = make_aligned_shared<CESAttributeVar<uint8>>(); break;
    case CES_QUATERNION: m_Value = make_aligned_shared<CESAttributeVar<quat_t>>(); break;
    case CES_DUAL_QUATERNION: m_Value = make_aligned_shared<CESAttributeVar<dquat_t>>(); break;
    case CES_VECTOR2: m_Value = make_aligned_shared<CESAttributeVar<vec2_t>>(); break;
    case CES_VECTOR3: m_Value = make_aligned_shared<CESAttributeVar<vec3_t>>(); break;
    case CES_VECTOR4: m_Value = make_aligned_shared<CESAttributeVar<vec4_t>>(); break;
    case CES_MATRIX3: m_Value = make_aligned_shared<CESAttributeVar<mat3_t>>(); break;
    case CES_MATRIX4: m_Value = make_aligned_shared<CESAttributeVar<mat4_t>>(); break;
    case CES_FILENAME: m_Value = make_aligned_shared<CESAttributeVar<filename_t>>(); break;
    case CES_FIXED_STRING: m_Value = make_aligned_shared<CESAttributeVar<fixed_string_t>>(); break;
    default:
        return false;
    }

    return m_Value->read(fs);
}

void CESAttribute::write(std::ostream& fs) const
{
    assert(m_Value);
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESAttributeHeader));
    m_Value->write(fs);
}

CESAttributeArray::CESAttributeArray()
{
    m_Header.size = 0;
    m_Header.type = CES_UNKNOWN;
}

CESAttributeArray::~CESAttributeArray()
{
}

CESAttributeArray::CESAttributeArray(const CESAttributeArray& ces_arr)
    :   m_Header(ces_arr.m_Header),
        m_Array(ces_arr.m_Array ? ces_arr.m_Array->clone() : CESAttributeValuePtr())
{
}

CESAttributeArray& CESAttributeArray::operator=(const CESAttributeArray& ces_arr)
{
    m_Header = ces_arr.m_Header;
    m_Array = ces_arr.m_Array ? ces_arr.m_Array->clone() : CESAttributeValuePtr();
    return *this;
}

CESVarType CESAttributeArray::getType() const
{
    return (CESVarType)m_Header.type;
}

CESAttributeValuePtr CESAttributeArray::clone() const
{
    return make_aligned_shared<CESAttributeArray>(*this);
}

bool CESAttributeArray::read(std::istream& fs)
{
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESArrayAttributeHeader));
    if(!fs)
        return false;
    switch(m_Header.type)
    {
    case CES_FLOAT:   m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<float> >>(*this); break;
    case CES_INT:     m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<int32> >>(*this); break;
    case CES_SHORT:   m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<int16> >>(*this); break;
    case CES_BYTE:    m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<int8> >>(*this); break;
    case CES_UINT:    m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<uint32> >>(*this); break;
    case CES_USHORT:  m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<uint16> >>(*this); break;
    case CES_UBYTE:   m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<uint8> >>(*this); break;
    case CES_QUATERNION: m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<quat_t> >>(*this); break;
    case CES_DUAL_QUATERNION: m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<dquat_t> >>(*this); break;
    case CES_VECTOR2: m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<vec2_t> >>(*this); break;
    case CES_VECTOR3: m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<vec3_t> >>(*this); break;
    case CES_VECTOR4: m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<vec4_t> >>(*this); break;
    case CES_MATRIX3: m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<mat3_t> >>(*this); break;
    case CES_MATRIX4: m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<mat4_t> >>(*this); break;
    case CES_FILENAME: m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<filename_t> >>(*this); break;
    case CES_FIXED_STRING: m_Array = make_aligned_shared<CESAttributeArrayValue< DynamicArray<fixed_string_t> >>(*this); break;
    default:
        return false;
    }
    return m_Array->read(fs);
}

void CESAttributeArray::write(std::ostream& fs) const
{
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESArrayAttributeHeader));
    m_Array->write(fs);
}

size_t CESAttributeArray::size() const
{
    return m_Header.size;
}

CESAttributeContainer::CESAttributeContainer()
{
    m_Header.attributes = 0;
}

CESAttributeContainer::~CESAttributeContainer()
{
}

CESAttributeContainer::CESAttributeContainer(const CESAttributeContainer& ces_ac)
    :   m_Header(ces_ac.m_Header),
        m_Attributes(ces_ac.m_Attributes)
{
}

CESAttributeContainer& CESAttributeContainer::operator=(const CESAttributeContainer& ces_ac)
{
    m_Header = ces_ac.m_Header;
    m_Attributes = ces_ac.m_Attributes;
    return *this;
}

bool CESAttributeContainer::read(std::istream& fs)
{
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESAttributeContainerHeader));
    if(!fs)
        return false;
    m_Attributes.resize(m_Header.attributes);
    for(size_t i = 0; i < m_Attributes.size(); ++i)
        if(!m_Attributes[i].read(fs))
            return false;
    return true;
}

void CESAttributeContainer::write(std::ostream& fs) const
{
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESAttributeContainerHeader));
    for(size_t i = 0; i < m_Attributes.size(); ++i)
        m_Attributes[i].write(fs);
}

size_t CESAttributeContainer::getAttributeCount() const
{
    return m_Attributes.size();
}

CESAttribute& CESAttributeContainer::getAttribute(size_t idx)
{
    return m_Attributes[idx];
}

CESAttribute* CESAttributeContainer::getAttribute(const string& name)
{
    for(size_t i = 0; i < m_Attributes.size(); ++i)
        if(m_Attributes[i] == name)
            return &m_Attributes[i];
    return 0;
}

CESGeometrySection::CESGeometrySection()
{
    m_Header.count = 0;
}

CESGeometrySection::~CESGeometrySection()
{
}

CESGeometrySection::CESGeometrySection(const CESGeometrySection& ces_geom)
    :   m_Header(ces_geom.m_Header),
        m_MeshRefs(ces_geom.m_MeshRefs)
{
}

CESGeometrySection& CESGeometrySection::operator=(const CESGeometrySection& ces_geom)
{
    m_Header = ces_geom.m_Header;
    m_MeshRefs = ces_geom.m_MeshRefs;
    return *this;
}

void CESGeometrySection::addMeshReference(const string& name, const CESMeshInfoPtr& mesh_info)
{
    m_MeshRefs.push_back(CESMeshReference(name, mesh_info));
    ++m_Header.count;
}

CESMeshReference* CESGeometrySection::getMeshReference(const string& name)
{
    for(size_t i = 0; i < m_MeshRefs.size(); ++i)
        if(m_MeshRefs[i].getMeshName() == name)
            return &m_MeshRefs[i];
    return 0;
}

CESMeshReference& CESGeometrySection::getMeshReference(size_t idx)
{
    return m_MeshRefs[idx];
}

size_t CESGeometrySection::getMeshRefCount() const
{
    return m_MeshRefs.size();
}

void CESGeometrySection::removeMeshReference(size_t idx)
{
    m_MeshRefs.erase(m_MeshRefs.begin() + idx);
}

void CESGeometrySection::removeMeshReference(size_t lhs, size_t rhs)
{
    m_MeshRefs.erase(m_MeshRefs.begin() + lhs, m_MeshRefs.begin() + rhs);
}

void CESGeometrySection::clear()
{
    m_Header.count = 0;
    m_MeshRefs.clear();
}

bool CESGeometrySection::read(std::istream& fs)
{
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESGeometrySectionHeader));
    if(!fs)
        return false;
    m_MeshRefs.resize(m_Header.count);
    for(size_t i = 0; i < m_MeshRefs.size(); ++i)
        if(!m_MeshRefs[i].read(fs))
            return false;
    return true;
}

void CESGeometrySection::write(std::ostream& fs) const
{
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESGeometrySectionHeader));
    for(size_t i = 0; i < m_MeshRefs.size(); ++i)
        m_MeshRefs[i].write(fs);
}

CESMeshReference::CESMeshReference()
{
}

CESMeshReference::~CESMeshReference()
{
}

CESMeshReference::CESMeshReference(const CESMeshReference& ces_mref)
    :   m_Header(ces_mref.m_Header),
        m_MeshInfo(ces_mref.m_MeshInfo ? ces_mref.m_MeshInfo->clone() : CESMeshInfoPtr())
{
}

CESMeshReference& CESMeshReference::operator=(const CESMeshReference& ces_mref)
{
    m_Header = ces_mref.m_Header;
    m_MeshInfo = ces_mref.m_MeshInfo ? ces_mref.m_MeshInfo->clone() : CESMeshInfoPtr();
    return *this;
}

CESMeshReference::CESMeshReference(const string& name, const CESMeshInfoPtr& mesh_info)
{
    m_Header.is_in_place = (uint8)mesh_info->isInPlace();
    copy_string_to_fixed(m_Header.name, sizeof(m_Header.name), name.c_str());
    m_MeshInfo = mesh_info;
}

string CESMeshReference::getMeshName() const
{
    return m_Header.name;
}

void CESMeshReference::setMeshName(const string& name)
{
    copy_string_to_fixed(m_Header.name, sizeof(m_Header.name), name.c_str());
}

CESMeshInfoPtr CESMeshReference::getMeshInfo()
{
    return m_MeshInfo;
}

void CESMeshReference::setMeshInfo(const CESMeshInfoPtr& mesh_info)
{
    m_Header.is_in_place = (uint8)mesh_info->isInPlace();
    m_MeshInfo = mesh_info;
}

bool CESMeshReference::read(std::istream& fs)
{
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESMeshReferenceHeader));
    if(m_Header.is_in_place)
        m_MeshInfo = make_aligned_shared<CESMeshInPlace>();
    else
        m_MeshInfo = make_aligned_shared<CESMeshFromFile>();
    return m_MeshInfo->read(fs);
}

void CESMeshReference::write(std::ostream& fs) const
{
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESMeshReferenceHeader));
    m_MeshInfo->write(fs);
}

CESMeshInfo::CESMeshInfo(bool in_place)
    :   m_InPlace(in_place)
{
}

CESMeshInfo::CESMeshInfo(const CESMeshInfo& ces_mi)
    :   m_InPlace(ces_mi.m_InPlace)
{
}

CESMeshInfo::~CESMeshInfo()
{
}

CESMeshInfo& CESMeshInfo::operator=(const CESMeshInfo& ces_mi)
{
    m_InPlace = ces_mi.m_InPlace;
    return *this;
}

CESMeshInPlace::CESMeshInPlace()
    :   CESMeshInfo(true)
{
}

CESMeshInPlace::~CESMeshInPlace()
{
}

CESMeshInPlace::CESMeshInPlace(const CESMeshInPlace& ces_mref)
    :   CESMeshInfo(ces_mref),
        m_Mesh(ces_mref.m_Mesh)
{
}

CESMeshInPlace& CESMeshInPlace::operator=(const CESMeshInPlace& ces_mref)
{
    CESMeshInfo::operator=(ces_mref);
    m_Mesh = ces_mref.m_Mesh;
    return *this;
}

CESMeshInfoPtr CESMeshInPlace::clone() const
{
    return make_aligned_shared<CESMeshInPlace>(*this);
}

CESMesh& CESMeshInPlace::getMesh()
{
    return m_Mesh;
}

bool CESMeshInPlace::read(std::istream& fs)
{
    return m_Mesh.read(fs);
}

void CESMeshInPlace::write(std::ostream& fs) const
{
    m_Mesh.write(fs);
}

CESMeshFromFile::CESMeshFromFile()
    :   CESMeshInfo(false)
{
}

CESMeshFromFile::~CESMeshFromFile()
{
}

CESMeshFromFile::CESMeshFromFile(const CESMeshFromFile& ces_mref)
    :   CESMeshInfo(ces_mref),
        m_FileName(ces_mref.m_FileName)
{
}

CESMeshFromFile& CESMeshFromFile::operator=(const CESMeshFromFile& ces_mref)
{
    CESMeshInfo::operator=(ces_mref);
    m_FileName = ces_mref.m_FileName;
    return *this;
}

CESMeshFromFile::CESMeshFromFile(const string& filename)
    :   CESMeshInfo(false)
{
    copy_string_to_fixed(m_FileName.name, sizeof(m_FileName.name), filename.c_str());
}

CESMeshInfoPtr CESMeshFromFile::clone() const
{
    return make_aligned_shared<CESMeshFromFile>(*this);
}

string CESMeshFromFile::getFileName() const
{
    return m_FileName.name;
}

void CESMeshFromFile::setFileName(const string& filename)
{
    copy_string_to_fixed(m_FileName.name, sizeof(m_FileName.name), filename.c_str());
}

bool CESMeshFromFile::read(std::istream& fs)
{
    fs.read(m_FileName.name, sizeof(filename_t));
    return fs.good();
}

void CESMeshFromFile::write(std::ostream& fs) const
{
    fs.write(m_FileName.name, sizeof(filename_t));
}


CESNode::CESNode()
{
    m_Header.children = 0;
}

CESNode::~CESNode()
{
    for(size_t i = 0; i < m_Children.size(); ++i)
        CE_DEALLOCATE(m_Children[i]);
}

CESNode::CESNode(const CESNode& ces_node)
    :   m_Header(ces_node.m_Header),
        m_Attributes(ces_node.m_Attributes),
        m_Children(ces_node.m_Children.size())
{
    for(size_t i = 0; i < m_Children.size(); ++i)
        m_Children[i] = CE_ALLOCATE(CESNode);
}

CESNode& CESNode::operator=(const CESNode& ces_node)
{
    m_Header = ces_node.m_Header;
    m_Attributes = ces_node.m_Attributes;
    clear();
    m_Children.resize(ces_node.m_Children.size());
    for(size_t i = 0; i < m_Children.size(); ++i)
        m_Children[i] = CE_ALLOCATE(CESNode);
    return *this;
}

CESNode& CESNode::addChild()
{
    ++m_Header.children;
    m_Children.push_back(CE_ALLOCATE(CESNode));
    return *m_Children.back();
}

CESNode& CESNode::getChild(size_t idx)
{
    return *m_Children[idx];
}

size_t CESNode::getChildrenCount() const
{
    return m_Children.size();
}

void CESNode::removeChild(size_t idx)
{
    --m_Header.children;
    CE_DEALLOCATE(m_Children[idx]);
    m_Children.erase(m_Children.begin() + idx);
}

void CESNode::removeChildren(size_t lhs, size_t rhs)
{
    m_Header.children -= rhs - lhs;
    for(size_t i = lhs; i < rhs; ++i)
        CE_DEALLOCATE(m_Children[i]);
    m_Children.erase(m_Children.begin() + lhs, m_Children.begin() + rhs);
}

void CESNode::clear()
{
    m_Header.children = 0;
    for(size_t i = 0; i < m_Children.size(); ++i)
        CE_DEALLOCATE(m_Children[i]);
    m_Children.clear();
}

CESAttributeContainer& CESNode::attributes()
{
    return m_Attributes;
}

bool CESNode::read(std::istream& fs)
{
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESNodeHeader));
    if(!fs)
        return false;
    if(!m_Attributes.read(fs))
        return false;
    m_Children.resize(m_Header.children);
    for(size_t i = 0; i < m_Children.size(); ++i)
    {
        m_Children[i] = CE_ALLOCATE(CESNode);
        if(!m_Children[i]->read(fs))
            return false;
    }
    return true;
}

void CESNode::write(std::ostream& fs) const
{
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESNodeHeader));
    m_Attributes.write(fs);
    for(size_t i = 0; i < m_Children.size(); ++i)
        m_Children[i]->write(fs);
}

CESEntitySection::CESEntitySection()
{
    m_Header.count = 0;
}

CESEntitySection::~CESEntitySection()
{
}

CESEntitySection::CESEntitySection(const CESEntitySection& ces_ent)
    :   m_Header(ces_ent.m_Header),
        m_Entities(ces_ent.m_Entities)
{
}

CESEntitySection& CESEntitySection::operator=(const CESEntitySection& ces_ent)
{
    m_Header = ces_ent.m_Header;
    m_Entities = ces_ent.m_Entities;
    return *this;
}

CESEntity& CESEntitySection::addEntity()
{
    ++m_Header.count;
    m_Entities.push_back(CESEntity());
    return m_Entities.back();
}

CESEntity& CESEntitySection::getEntity(size_t idx)
{
    return m_Entities[idx];
}

CESEntity* CESEntitySection::getEntity(const string& name)
{
    string ent_name;
    for(size_t i = 0; i < m_Entities.size(); ++i)
    {
        m_Entities[i].getAttribute("Name", ent_name);
        if(ent_name == name)
            return &m_Entities[i];
    }
    return 0;
}

size_t CESEntitySection::getEntityCount() const
{
    return m_Entities.size();
}

void CESEntitySection::removeEntity(size_t idx)
{
    m_Entities.erase(m_Entities.begin() + idx);
}

void CESEntitySection::removeEntity(size_t lhs, size_t rhs)
{
    m_Entities.erase(m_Entities.begin() + lhs, m_Entities.begin() + rhs);
}

void CESEntitySection::clear()
{
    m_Header.count = 0;
    m_Entities.clear();
}

bool CESEntitySection::read(std::istream& fs)
{
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESEntitySectionHeader));
    m_Entities.resize(m_Header.count);
    for(size_t i = 0; i < m_Entities.size(); ++i)
        if(!m_Entities[i].read(fs))
            return false;
    return true;
}

void CESEntitySection::write(std::ostream& fs) const
{
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESEntitySectionHeader));
    for(size_t i = 0; i < m_Entities.size(); ++i)
        m_Entities[i].write(fs);
}

CESGlobalLightSection::CESGlobalLightSection()
{
    m_Header.count = 0;
    m_AmbientLight.setAttribute("Color", vec3_zero);
}

CESGlobalLightSection::~CESGlobalLightSection()
{
}

CESGlobalLightSection::CESGlobalLightSection(const CESGlobalLightSection& ces_gls)
    :   m_Header(ces_gls.m_Header),
        m_AmbientLight(ces_gls.m_AmbientLight),
        m_GlobalLightSources(ces_gls.m_GlobalLightSources)
{
}

CESGlobalLightSection& CESGlobalLightSection::operator=(const CESGlobalLightSection& ces_gls)
{
    m_Header = ces_gls.m_Header;
    m_AmbientLight = ces_gls.m_AmbientLight;
    m_GlobalLightSources = ces_gls.m_GlobalLightSources;
    return *this;
}

CESAttributeContainer& CESGlobalLightSection::addGlobalLight()
{
    ++m_Header.count;
    m_GlobalLightSources.push_back(CESAttributeContainer());
    return m_GlobalLightSources.back();
}

CESAttributeContainer& CESGlobalLightSection::getGlobalLight(size_t idx)
{
    return m_GlobalLightSources[idx];
}
    
size_t CESGlobalLightSection::getGlobalLightCount() const
{
    return m_GlobalLightSources.size();
}
    
void CESGlobalLightSection::removeGlobalLight(size_t idx)
{
    --m_Header.count;
    m_GlobalLightSources.erase(m_GlobalLightSources.begin() + idx);
}

void CESGlobalLightSection::removeGlobalLight(size_t lhs, size_t rhs)
{
    m_Header.count -= rhs - lhs;
    m_GlobalLightSources.erase(m_GlobalLightSources.begin() + lhs, m_GlobalLightSources.begin() + rhs);
}
    
void CESGlobalLightSection::clear()
{
    m_Header.count = 0;
    m_GlobalLightSources.clear();
    m_AmbientLight.setAttribute("Color", vec3_zero);
}

void CESGlobalLightSection::getAmbientLight(vec3_t& color) const
{
    m_AmbientLight.getAttribute("Color", color);
}

void CESGlobalLightSection::setAmbientLight(const vec3_t& color)
{
    m_AmbientLight.setAttribute("Color", color);
}

bool CESGlobalLightSection::read(std::istream& fs)
{
    fs.read(reinterpret_cast<char*>(&m_Header), sizeof(CESGlobalLightSectionHeader));
    if(!fs)
        return false;
    if(!m_AmbientLight.read(fs))
        return false;
    m_GlobalLightSources.resize(m_Header.count);
    for(size_t i = 0; i < m_GlobalLightSources.size(); ++i)
        if(!m_GlobalLightSources[i].read(fs))
            return false;
    return true;
}

void CESGlobalLightSection::write(std::ostream& fs) const
{
    fs.write(reinterpret_cast<const char*>(&m_Header), sizeof(CESGlobalLightSectionHeader));
    m_AmbientLight.write(fs);
    for(size_t i = 0; i < m_GlobalLightSources.size(); ++i)
        m_GlobalLightSources[i].write(fs);
}

CEMFile::CEMFile()
{
    m_Header.magic_number = cem_magic_number;
    m_Header.major_version = cem_major_version;
    m_Header.minor_version = cem_minor_version;
}

CEMFile::~CEMFile()
{
}

CEMFile::CEMFile(const CEMFile& cem_file)
    :   m_Header(cem_file.m_Header),
        m_Mesh(cem_file.m_Mesh)
{
}

CEMFile& CEMFile::operator=(const CEMFile& cem_file)
{
    m_Header = cem_file.m_Header;
    m_Mesh = cem_file.m_Mesh;
    return *this;
}

CEMMesh& CEMFile::getMesh()
{
    return m_Mesh;
}

bool CEMFile::save(const string& filename) const
{
    std::ofstream fs(filename.c_str(), std::ios::binary | std::ios::out);
    if(!fs)
        return false;
    m_Mesh.write(fs);
    return true;
}

bool CEMFile::open(const string& filename)
{
    std::ifstream fs(filename.c_str(), std::ios::binary | std::ios::in);
    if(!fs)
        return false;
    if(m_Header.magic_number != cem_magic_number ||
       m_Header.major_version != cem_major_version ||
       m_Header.minor_version != cem_minor_version)
        return false;
    return m_Mesh.read(fs);
}
}
